package com.fjzcit.mysqltuning.service.impl;

import com.fjzcit.mysqltuning.service.OOMService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author Atu
 * @Date 2024/6/23
 * @Version 1.0
 * 模拟 JVM 堆内存溢出的服务实现类
 */
@Service
@Slf4j
public class OOMServiceImpl implements OOMService {
	
	// 统计信息
	private final AtomicInteger oomCount = new AtomicInteger(0);
	private final AtomicInteger totalAllocatedMB = new AtomicInteger(0);
	
	@Override
	public String triggerOOM() {
		log.info("开始触发堆内存溢出...");
		
		List<byte[]> memoryHolder = new ArrayList<>();
		int count = 0;
		long startTime = System.currentTimeMillis();
		
		try {
			while (true) {
				// 创建大对象
				byte[] bigObject = createMemoryBlock(10);
				memoryHolder.add(bigObject);
				count++;
				
				// 更新统计信息
				totalAllocatedMB.addAndGet(10);
			}
			
		} catch (OutOfMemoryError e) {
			long duration = System.currentTimeMillis() - startTime;
			oomCount.incrementAndGet();
			
			log.error("发生堆内存溢出错误 - 已分配 {} 个内存块，耗时 {} ms，累计 OOM 次数: {}", 
				count, duration, oomCount.get(), e);
			
			return String.format("堆内存溢出已触发！已分配 %d 个内存块，耗时 %d ms，错误信息: %s。内存已自动释放，可以继续使用。", 
				count, duration, e.toString());
		}
	}
	
	@Override
	public String allocateMemory(int sizeMB) {
		if (sizeMB <= 0 || sizeMB > 1000) {
			return "内存大小必须在 1-1000 MB 之间";
		}
		
		log.info("开始申请 {} MB 内存...", sizeMB);
		
		try {
			byte[] block = createMemoryBlock(sizeMB);
			totalAllocatedMB.addAndGet(sizeMB);
			
			log.info("成功申请 {} MB 内存", sizeMB);
			return String.format("成功申请 %d MB 内存，方法结束后将自动释放", sizeMB);
			
		} catch (OutOfMemoryError e) {
			oomCount.incrementAndGet();
			totalAllocatedMB.addAndGet(sizeMB);
			
			log.error("申请 {} MB 内存时发生 OOM", sizeMB, e);
			return String.format("申请内存失败，发生 OOM: %s", e.toString());
		}
	}
	
	/**
	 * 创建指定大小的内存块
	 */
	private byte[] createMemoryBlock(int sizeMB) {
		byte[] block = new byte[sizeMB * 1024 * 1024];
		// 触碰内存页，确保真正分配
		for (int i = 0; i < block.length; i += 1024 * 1024) {
			block[i] = 1;
		}
		return block;
	}
} 